package com.idea.relax.tool.wechat.toolkit;

import com.idea.relax.tool.wechat.config.WechatPayProperties;
import com.idea.relax.tool.wechat.model.*;
import com.wechat.pay.java.core.exception.ServiceException;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.payments.jsapi.JsapiServiceExtension;
import com.wechat.pay.java.service.payments.jsapi.model.*;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Map;

/**
 * @author: 沉香
 * @date: 2023/3/7
 * @description:
 */
@Slf4j
@AllArgsConstructor
public class WechatPayHelper {

	private final WechatPayProperties properties;

	private final JsapiServiceExtension jsapiService;


	private SignatureRequestDTO fillDtoByHeader(HttpServletRequest request, WechatPayCallbackVO wechatPayCallbackVO) {
		SignatureRequestDTO signatureRequestDTO = new SignatureRequestDTO();
		signatureRequestDTO.setSerial(request.getHeader("Wechatpay-Serial"));
		signatureRequestDTO.setNonce(request.getHeader("Wechatpay-Nonce"));
		signatureRequestDTO.setSignatureType(request.getHeader("Wechatpay-Signature-Type"));
		signatureRequestDTO.setBody(JsonUtil.toJson(wechatPayCallbackVO));
		signatureRequestDTO.setTimeStamp(request.getHeader("Wechatpay-Timestamp"));
		signatureRequestDTO.setSignature(request.getHeader("Wechatpay-Signature"));
		return signatureRequestDTO;
	}

	/**
	 * 发起微信支付
	 *
	 * @param openId
	 * @param orderRequestDTO
	 * @return
	 * @throws Exception
	 */
	public PayResponseDTO wechatPay(String openId, OrderRequestDTO orderRequestDTO) {
		// request.setXxx(val)设置所需参数，具体参数可见Request定义
		PrepayRequest request = new PrepayRequest();
		request.setAppid(properties.getAppId());
		request.setMchid(properties.getMchId());
		request.setDescription(orderRequestDTO.getDescription());
		// 支付回调地址
		request.setNotifyUrl(properties.getNotifyUrl());
		request.setOutTradeNo(orderRequestDTO.getOutTradeNo());
		// 订单金额
		Amount amount = new Amount();
		//140.53 ==> 14053
		amount.setTotal(orderRequestDTO.getAmount());
		amount.setCurrency("CNY");
		request.setAmount(amount);
		Payer payer = new Payer();
		payer.setOpenid(openId);
		request.setPayer(payer);
		PrepayWithRequestPaymentResponse response = jsapiService.prepayWithRequestPayment(request);
		PayResponseDTO payResponseDTO = new PayResponseDTO();
		payResponseDTO.setPaySign(response.getPaySign());
		payResponseDTO.setTimestamp(response.getTimeStamp());
		payResponseDTO.setAppId(response.getAppId());
		payResponseDTO.setNonceStr(response.getNonceStr());
		payResponseDTO.setSignType(response.getSignType());
		payResponseDTO.setPackageVal(response.getPackageVal());
		return payResponseDTO;
	}

	/**
	 * 微信支付回调<br>
	 * 用户支付完成后，微信会把相关支付结果和用户信息发送给商户，商户需要接收处理该消息，并返回应答。<br>
	 * 对后台通知交互时，如果微信收到商户的应答不符合规范或超时，微信认为通知失败，<br>
	 * 微信会通过一定的策略定期重新发起通知，尽可能提高通知的成功率，但微信不保证通知最终能成功。<br>
	 * （通知频率为15s/15s/30s/3m/10m/20m/30m/30m/30m/60m/3h/3h/3h/6h/6h - 总计 24h4m）
	 * 状态失败
	 * 状态成功
	 *
	 * @param wechatPayCallbackVO
	 * @param request
	 * @return
	 * @throws Exception
	 */
	public WechatPayResponseDTO wechatPayCallback(WechatPayCallbackVO wechatPayCallbackVO,
												  HttpServletRequest request) throws GeneralSecurityException, IOException {
		WechatPayResourceVO resource = wechatPayCallbackVO.getResource();
		SignatureRequestDTO signatureRequestDTO = fillDtoByHeader(request, wechatPayCallbackVO);
		// 构造 RequestParam
		RequestParam requestParam = new RequestParam.Builder()
			.serialNumber(properties.getMchSerialNo())
			.nonce(resource.getNonce())
			.signature(signatureRequestDTO.getSignature())
			.timestamp(signatureRequestDTO.getTimeStamp())
			.signType(signatureRequestDTO.getSignatureType())
			.body(signatureRequestDTO.getBody())
			.build();
		AesUtil aesUtil = new AesUtil(properties.getV3Secret().getBytes());
		String requestParamBody = requestParam.getBody();
		Map<String, Object> requestBody = JsonUtil.toMap(requestParamBody);
		Map<String, Object> resourceMap = (Map) requestBody.get("resource");
		String decryptToString = aesUtil.decryptToString(
			((String) resourceMap.get("associated_data")).getBytes(),
			((String) resourceMap.get("nonce")).getBytes(),
			(String) resourceMap.get("ciphertext")
		);
		return JsonUtil.parse(decryptToString, WechatPayResponseDTO.class);
	}

	/**
	 * 查询订单<br>
	 * 需要调用该接口的情况：
	 * • 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知。
	 * • 调用支付接口后，返回系统错误或未知交易状态情况。
	 * • 调用付款码支付API，返回USERPAYING的状态。
	 * • 调用关单或撤销接口API之前，需确认支付状态。
	 *
	 * @param mchId
	 * @param transactionId
	 * @return
	 */
	public OrderResponseDTO queryOrderByTransactionId(String mchId, String transactionId) {
		QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
		queryRequest.setMchid(mchId);
		queryRequest.setTransactionId(transactionId);
		try {
			return new OrderResponseDTO(jsapiService.queryOrderById(queryRequest));
		} catch (ServiceException e) {
			// API返回失败, 例如ORDER_NOT_EXISTS
			log.error("code=[{}], message=[{}]", e.getErrorCode(), e.getErrorMessage());
			log.error("response body=[{}]", e.getResponseBody());
		}
		return null;
	}

	/**
	 * 查询订单<br>
	 * 需要调用该接口的情况：
	 * • 当商户后台、网络、服务器等出现异常，商户系统最终未接收到支付通知。
	 * • 调用支付接口后，返回系统错误或未知交易状态情况。
	 * • 调用付款码支付API，返回USERPAYING的状态。
	 * • 调用关单或撤销接口API之前，需确认支付状态。
	 *
	 * @param mchId
	 * @param outTradeNo
	 * @return
	 */
	public OrderResponseDTO queryOrderByOutTradeNo(String mchId, String outTradeNo) {
		QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
		request.setMchid(mchId);
		request.setOutTradeNo(outTradeNo);
		try {
			return new OrderResponseDTO(jsapiService.queryOrderByOutTradeNo(request));
		} catch (ServiceException e) {
			// API返回失败, 例如ORDER_NOT_EXISTS
			log.error("code=[{}], message=[{}]", e.getErrorCode(), e.getErrorMessage());
			log.error("response body=[{}]", e.getResponseBody());
		}
		return null;
	}

	/**
	 * 关闭订单<br>
	 * 以下情况需要调用关单接口：
	 * 1、商户订单支付失败需要生成新单号重新发起支付，要对原订单号调用关单，避免重复支付；
	 * 2、系统下单后，用户支付超时，系统退出不再受理，避免用户继续，请调用关单接口。
	 *
	 * @param mchId
	 * @param outTradeNo
	 */
	public void closeOrder(String mchId, String outTradeNo) {
		CloseOrderRequest closeRequest = new CloseOrderRequest();
		closeRequest.setMchid(mchId);
		closeRequest.setOutTradeNo(outTradeNo);
		// 方法没有返回值，意味着成功时API返回204 No Content
		jsapiService.closeOrder(closeRequest);
	}


}
